/*
 * Copyright (C) 2010, Funcom AS, All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  @authors
 *  Svein Gunnar Larsen <sveingunnarlarsen@gmail.com>
 *  Erik Thune Lund <culex.eriklund@gmail.com>
 *  Michel Krohn-Dale <kronicz@gmail.com>
 */

package com.funcom.peeler;

import com.funcom.peeler.action.StyleChange;
import com.funcom.peeler.peelable.*;
import com.funcom.peeler.properties.PropertiesFrame;
import com.funcom.peeler.styleProperties.EffectColor;
import com.funcom.peeler.styleProperties.EffectSize;
import com.funcom.peeler.symbol.Symbol;
import com.funcom.peeler.tool.ToolPanel;
import com.jme.renderer.ColorRGBA;
import com.jmex.bui.BComponent;
import com.jmex.bui.Rule;
import com.jmex.bui.enumeratedConstants.HorizontalAlignment;
import com.jmex.bui.enumeratedConstants.TextEffect;
import com.jmex.bui.enumeratedConstants.VerticalAlignment;
import com.jmex.bui.property.BackgroundProperty;
import com.jmex.bui.property.CursorProperty;
import com.jmex.bui.property.FontProperty;
import com.jmex.bui.property.IconProperty;
import com.jmex.bui.text.BTextFactory;
import com.jmex.bui.util.Insets;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.KeyEvent;
import java.util.*;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static com.funcom.peeler.component.PeelComponentFactory.makePeelComponent;

/**
 * All purpose static communication singleton class for passing messages
 * and objects between windows, acting on WorkBenches, getting the PropertyWindow
 * or ToolBox and so on. Also contains global AWT listener for global hotkeys.
 */
public class UpdateCont {

    private static UpdateCont ourInstance = new UpdateCont();

    private ToolPanel toolbox;
    private PropertiesFrame propFrame;

    private JFileChooser chooser = new JFileChooser( System.getProperty( "user.dir" ) );

    private Symbol.BType typeToAdd = null;
    private WorkBench focusedWorkbench = null;

    private PeelComponent componentCopy = null;
    private boolean hasCopy = false;

    private Set< WorkBench > workbenches = new HashSet< WorkBench >();

    Lock lock = new ReentrantLock();

    /**
     * Gets the instance of UpdateCont
     * @return the instance of UpdateCont
     */
    public static UpdateCont get() {
        return ourInstance;
    }

    /**
     * Create the UpdateCont and init the global listener
     */
    private UpdateCont() {
        initAWTListener();
    }

    public void setToolbox( ToolPanel toolbox ) {
        if ( toolbox == null ) return;

        this.toolbox = toolbox;
    }

    public void setPropertiesFrame( PropertiesFrame propFrame ){
        if( propFrame == null) return;

        this.propFrame = propFrame;
    }

    public PropertiesFrame getPropFrame(){
        return propFrame;
    }

    public ToolPanel getToolbox() {
        return toolbox;
    }

    public final Set< WorkBench > getWorkBenches() {
        return workbenches;
    }

    public Rule getStyleClassProp(String styleClass) {
        if (focusedWorkbench.getWorkBenchStyle().properties.containsKey(styleClass)) {
            return focusedWorkbench.getWorkBenchStyle().properties.get(styleClass);
        } else {
            return null;
        }
    }

    public void sendToWorkBench(BComponent component) {
        typeToAdd = (Symbol.BType) component.getProperty("Type");
    }

    public boolean hasCopy() {
        return hasCopy;
    }

    public void deleteCopy() {
        componentCopy = null;
        hasCopy = false;
    }

    public void sendComponentCopy( PeelComponent component ) {
        componentCopy = component;
        hasCopy = true;
    }

    public PeelComponent getComponentCopy() {
        return componentCopy;
    }

    public void componentHierarchyChanged() {
        propFrame.setHierarchyChange(true);
    }

    public void newOrDeleteChange(boolean val) {
        propFrame.setNewOrDelete(val);
    }

    public void updateStyleClassRules(PeelComponent component, String pseudoClass, Object new_value) {

        String styleClass = component.getComponent().getStyleClass();
        PeelStyleSheet stylesheet = (PeelStyleSheet)component.getComponent().getWindow().getStyleSheet();

        if (pseudoClass != null) {
            styleClass += ":" + pseudoClass;
        }

        Rule rule = stylesheet.properties.get( styleClass );

        if (rule == null) {
            stylesheet.updateStyle(styleClass, null, null);
            rule = stylesheet.properties.get( styleClass );
        }

        if (new_value instanceof BackgroundProperty) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.background );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.background, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "background", new_value);
        }

        if (new_value instanceof ColorRGBA) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.color );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.color, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "color", new_value);
        }

        if (new_value instanceof IconProperty) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.icon );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.icon, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "icon", new_value);
        }

        if (new_value instanceof CursorProperty) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.cursor );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.cursor, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "cursor", new_value);
        }

        if ((new_value instanceof BTextFactory) || (new_value instanceof FontProperty)) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.font );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.font, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "font", new_value);

            // For some strange reason the font dosen't resolve itself unless we change some other text property
            // so we just give the class another text-effect and then set it back to the old one. And we have
            // to make sure we don't try to resolve it with a setting it already has.
            Object value = rule.properties.get( Symbol.StyleProperty.text_effect );
            Object resolveFont = TextEffect.fromInt(3);

            if ((resolveFont).equals(value)) {
                resolveFont = TextEffect.fromInt(0);
            }

            stylesheet.updateStyle(styleClass, "text-effect", resolveFont);

            invalidateAll();
            stylesheet.updateStyle(styleClass, "text-effect", value);
        }

        if (new_value instanceof HorizontalAlignment) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.text_align );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.text_align, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "text-align", new_value);
        }

        if (new_value instanceof VerticalAlignment) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.vertical_align );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.vertical_align, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "vertical-align", new_value);
        }

        if (new_value instanceof TextEffect) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.text_effect );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.text_effect, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "text-effect", new_value);
        }

        if (new_value instanceof Insets) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.padding );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.padding, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "padding", new_value);
        }
        if (new_value instanceof EffectSize) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.effect_size );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.padding, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "effect-size", ((EffectSize) new_value).size);
        }
        if (new_value instanceof EffectColor) {
            Object old_value = rule.properties.get( Symbol.StyleProperty.effect_color );
            getFocusedWorkBench().pushChange( new StyleChange( stylesheet, styleClass, Symbol.StyleProperty.padding, old_value, new_value ) );
            stylesheet.updateStyle(styleClass, "effect-color", ((EffectColor) new_value).colorRGBA);
        }

        invalidateAll();
    }

    /**
     * Invalidates the selected component and optionally sets a new styleclass
     * for it during invalidation
     * @param component the PeelComponent to invalidate
     * @param newStyle whether a new styleclass is to be set
     * @param style the new styleclass to be set( can be null )
     */
    public void invalidateComponent( PeelComponent component, boolean newStyle, String style) {

        if (component == null) return;

        // Remove all the backgrounds, check for types with extra states
        component.getComponent().setBackground(0, null);
        component.getComponent().setBackground(1, null);
        component.getComponent().setBackground(2, null);

        if (component instanceof PeelButton) {
            component.getComponent().setBackground(3, null);
        }

        if (component instanceof PeelToggleButton) {
            component.getComponent().setBackground(4, null);
            component.getComponent().setBackground(5, null);
        }

        if (component instanceof PeelTextComponent) {

        }

        PeelContainer tempParent = component.getParent();

        if (tempParent == null) return;

        int index = tempParent.getChildIndex( component );

        tempParent.removeChild(component);
        if (newStyle) {
            component.getComponent().setStyleClass(style);
        }
        tempParent.addChild( index, component );
    }

    /**
     * Invalidates all the components on the focused WorkBench
     */
    public void invalidateAll() {

        Set< PeelComponent > allComp = focusedWorkbench.getBui().peelroot.getAllComponents();

        for ( PeelComponent aComp : allComp) {
            invalidateComponent(aComp, false, null);
        }
    }

    /**
     * Checks if a type to construct has been selected
     * @return true if a new type to construct is selected
     */
    public boolean newComponentReady() {
        return typeToAdd != null;
    }

    /**
     * Sets the last focused WorkBench
     * @param workbench the WorkBench to report as last focused
     */
    public void setFocusedWorkBench( WorkBench workbench ) {
        focusedWorkbench = workbench;
    }

    /**
     * Gets the last focused WorkBench
     * @return the last focused WorkBench
     */
    public WorkBench getFocusedWorkBench() {
        return focusedWorkbench;
    }

    /**
     * Gets a new PeelComponent from the factory if a type has been requested
     * @return the new PeelComponent
     */
    public PeelComponent getNewComponent() {

        if ( typeToAdd == null ) return null;

        return makePeelComponent(typeToAdd);
    }

    /**
     * Registers a WorkBench to the workbench list
     * @param workbench the WorkBench to register
     */
    public void registerWorkbench( WorkBench workbench ) {
        lock.lock();
        try {
            workbenches.add( workbench );
        } finally {
            lock.unlock();
        }
    }

    /**
     * Deregisters a WorkBench from the workbench list
     * @param workbench the WorkBench to deregister
     */
    public void deregisterWorkBench( WorkBench workbench ) {
        lock.lock();
        try {
            workbenches.remove( workbench );
        } finally {
            lock.unlock();
        }
    }

    /**
     * Tells all WorkBenches to close down
     */
    public void closeAllWorkBenches() {

        List< WorkBench > workbenches_copy;

        lock.lock();
        try {
            // Make a copy of the workbench list, as it will be modified when workbenches are closed
            workbenches_copy = new LinkedList< WorkBench >( workbenches );
        } finally {
            lock.unlock();
        }

        for ( WorkBench workbench : workbenches_copy ) {
            workbench.close();
        }
    }

    /**
     * Global listener that takes care of registering undo/redo on the last focused WorkBench,
     * loading files into new WorkBenches, saving the last focused WorkBench and opening new
     * blank WorkBenches with hotkeys
     */
    private void initAWTListener() {

        Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener() {

            public void eventDispatched(AWTEvent e ) {

                if ( e instanceof KeyEvent) {

                    KeyEvent eKey = (KeyEvent)e;

                    if ( eKey.getID() != KeyEvent.KEY_PRESSED ) return;

                    switch( eKey.getKeyCode() ) {

                        // Load a new WorkBench from file if Ctrl + O is pressed
                        case KeyEvent.VK_O: {
                            if ( eKey.isControlDown() ) loadNewWorkBench();
                            break;
                        }

                        case KeyEvent.VK_N: {
                            if ( eKey.isControlDown() ) new WorkBench();
                            break;
                        }

                        // Save the WorkBench to file if Ctrl + S is pressed
                        case KeyEvent.VK_S: {
                            if ( eKey.isControlDown() ) {
                                WorkBench workbench = getFocusedWorkBench();
                                if ( workbench == null ) return;
                                    workbench.saveWorkSpace();
                            }
                            break;
                        }

                        // Redo the last change on the last focused WorkBench if Ctrl + Y is pressed
                        case KeyEvent.VK_Y: {

                            WorkBench workbench = getFocusedWorkBench();
                            if ( workbench == null ) break;

                            if ( eKey.isControlDown() ) {
                                workbench.redo();
                            }
                            break;
                        }

                        // Undo the last change on the last focused WorkBench if Ctrl + Z is pressed
                        case KeyEvent.VK_Z: {

                            WorkBench workbench = getFocusedWorkBench();
                            if ( workbench == null ) break;

                            if ( eKey.isControlDown() ) {
                                if ( eKey.isShiftDown() ) {
                                    workbench.redo();
                                }
                                else {
                                    workbench.undo();
                                }
                            }
                            break;
                        }

                        case KeyEvent.VK_ESCAPE: {

                            WorkBench workbench = getFocusedWorkBench();
                            if ( workbench == null ) break;

                            workbench.getBui().clearSelections();
                        }
                    }
                }
            }
        }, AWTEvent.KEY_EVENT_MASK );
    }

    /**
     * Creates a new workbench and loads the selected file into it
     */
    public void loadNewWorkBench() {

        FileFilter xmlFilter = new ExtensionFileFilter(".xml", new String[] { "xml"});
        chooser.setFileFilter(xmlFilter);
        chooser.setDialogTitle("Loading Workbench Area");
        int result = chooser.showOpenDialog( null );
        switch ( result ) {
            case JFileChooser.APPROVE_OPTION:
                new WorkBench( chooser.getSelectedFile().getPath() );
                break;
        }
    }
}
